<?php

namespace common\components\aliyun;

use Yii;
use OSS\OssClient;
use OSS\Core\OssUtil;
use OSS\Core\OssException;
use yii\base\InvalidCallException;
use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;

/**
 * AliyunOSS
 *
 * @author emhome
 */
class AliyunOSS extends \yii\base\Component {

    public $accessKeyId = null;
    public $accessKeySecret = null;
    public $endpoint = null;
    public $bucket = null;
    public $host = null;
    public $usests = false;
    //
    private $_client = null;

    const STS_AUTH_KEY = 'alists';

    /**
     * @inheritdoc
     * @throws InvalidConfigException
     */
    public function init() {
        if ($this->accessKeyId === null) {
            throw new InvalidConfigException('The "accessKeyId" property must be set.');
        } elseif ($this->accessKeySecret === null) {
            throw new InvalidConfigException('The "accessKeySecret" property must be set.');
        } elseif ($this->endpoint === null) {
            throw new InvalidConfigException('The "endpoint" property must be set.');
        } elseif ($this->bucket === null) {
            throw new InvalidConfigException('The "bucket" property must be set.');
        } elseif ($this->host === null) {
            throw new InvalidConfigException('The "host" property must be set.');
        }
        try {
            $accessKeyId = $this->accessKeyId;
            $accessKeySecret = $this->accessKeySecret;
            $endpoint = $this->endpoint;
            $securityToken = null;
            if ($this->usests) {
                $temp = $this->getStsAuthData();
                if (!empty($temp)) {
                    $accessKeyId = ArrayHelper::getValue($temp, 'AccessKeyId');
                    $accessKeySecret = ArrayHelper::getValue($temp, 'AccessKeySecret');
                    $securityToken = ArrayHelper::getValue($temp, 'SecurityToken');
                }
            }
            $this->_client = new OssClient($accessKeyId, $accessKeySecret, $endpoint, false, $securityToken);
        } catch (OssException $e) {
            throw new InvalidCallException($e->getMessage());
        }
    }

    public function getStsAuthData() {
        $cache = Yii::$app->cache;
        $data = $cache->get(self::STS_AUTH_KEY);
        if ($data === false) {
            $data = Yii::$app->alists->authent();
            $expiration = ArrayHelper::getValue($data, 'Expiration');
            $duration = strtotime($expiration) - time() - 30;
            $cache->set(self::STS_AUTH_KEY, $data, $duration);
        }
        return $data;
    }

    public function getClient() {
        return $this->_client;
    }

    public function putObjectACL($filename, $acl) {
        try {
            return $this->client->putObjectAcl($this->bucket, $filename, $acl);
        } catch (OssException $e) {
            return false;
        }
    }

    public function putObject($filename, $content, $options = null) {
        return $this->client->putObject($this->bucket, $filename, $content, $options);
    }

    /**
     * 上传文件
     * @param type $filename
     * @param type $path
     * @param type $acl  读写权限，可选值['private', 'public-read', 'public-read-write']
     * @return type
     */
    public function uploadFile($filename, $path, $acl = 'private') {
        $client = $this->client;
        $client->createObjectDir($this->bucket, dirname($filename));
        $client->uploadFile($this->bucket, $filename, $path);
        try {
            $client->putObjectAcl($this->bucket, $filename, $acl);
            return true;
        } catch (OssException $e) {
            return false;
        }
    }

    public function partUpload($filename, $path, $acl = 'private') {
        $client = $this->client;
        $client->createObjectDir($this->bucket, dirname($filename));

        $object = $filename;
        $uploadFile = $path;
        /**
         *  步骤1：初始化一个分片上传事件，获取uploadId。
         */
        try {
            $ossClient = $this->client;
            //返回uploadId，它是分片上传事件的唯一标识，您可以根据这个ID来发起相关的操作，如取消分片上传、查询分片上传等。
            $uploadId = $ossClient->initiateMultipartUpload($this->bucket, $object);
        } catch (OssException $e) {
            Yii::error($e->getMessage(), __METHOD__);
            return false;
        }
        /*
         * 步骤2：上传分片。
         */
        $partSize = 10 * 1024 * 1024;
        $uploadFileSize = filesize($uploadFile);
        $pieces = $ossClient->generateMultiuploadParts($uploadFileSize, $partSize);
        $responseUploadPart = array();
        $uploadPosition = 0;
        $isCheckMd5 = true;
        foreach ($pieces as $i => $piece) {
            $fromPos = $uploadPosition + (integer) $piece[$ossClient::OSS_SEEK_TO];
            $toPos = (integer) $piece[$ossClient::OSS_LENGTH] + $fromPos - 1;
            $upOptions = array(
                $ossClient::OSS_FILE_UPLOAD => $uploadFile,
                $ossClient::OSS_PART_NUM => ($i + 1),
                $ossClient::OSS_SEEK_TO => $fromPos,
                $ossClient::OSS_LENGTH => $toPos - $fromPos + 1,
                $ossClient::OSS_CHECK_MD5 => $isCheckMd5,
            );
            // MD5校验。
            if ($isCheckMd5) {
                $contentMd5 = OssUtil::getMd5SumForFile($uploadFile, $fromPos, $toPos);
                $upOptions[$ossClient::OSS_CONTENT_MD5] = $contentMd5;
            }
            try {
                // 上传分片。
                $responseUploadPart[] = $ossClient->uploadPart($bucket, $object, $uploadId, $upOptions);
            } catch (OssException $e) {
                Yii::error($e->getMessage(), __METHOD__);
                return false;
            }
        }
        // $uploadParts是由每个分片的ETag和分片号（PartNumber）组成的数组。
        $uploadParts = array();
        foreach ($responseUploadPart as $i => $eTag) {
            $uploadParts[] = array(
                'PartNumber' => ($i + 1),
                'ETag' => $eTag,
            );
        }
        /**
         * 步骤3：完成上传。
         */
        try {
            // 在执行该操作时，需要提供所有有效的$uploadParts。OSS收到提交的$uploadParts后，会逐一验证每个分片的有效性。当所有的数据分片验证通过后，OSS将把这些分片组合成一个完整的文件。
            $ossClient->completeMultipartUpload($bucket, $object, $uploadId, $uploadParts);
        } catch (OssException $e) {
            Yii::error($e->getMessage(), __METHOD__);
            return false;
        }

        $ossClient->putObjectAcl($this->bucket, $filename, $acl);
        return true;
    }

}
